﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Opennav.MapGenerator.Geometry;
using System.IO;
using System.Diagnostics;
using Opennav.MapGenerator.Map.Roads;
using Opennav.MapGenerator.Map.Labels;

namespace Opennav.MapGenerator.Map.MapTiles
{
    public class MapLine : MapObject
    {
        private MapLineType _type;
        private Coordinate[] _coordinates;
        private Label _label = null;
        private RoadSegment _roadSegment = null;
        private Area _bounds = new Area();

        public MapLine(MapLine src, IEnumerable<Coordinate> coordinates)
            : this(src.Type, coordinates, src.RoadSegment, src.Label, src.MinResolution, src.MaxResolution) { }

        public MapLine(MapLineType type, IEnumerable<Coordinate> coordinates, int minResolution, int maxResolution)
            : this(type, coordinates, null, null, minResolution, maxResolution) { }

        public MapLine(MapLineType type, IEnumerable<Coordinate> coordinates, RoadSegment segment, int minResolution, int maxResolution)
            : this(type, coordinates, segment, null, minResolution, maxResolution) { }

        public MapLine(MapLineType type, IEnumerable<Coordinate> coordinates, Label label, int minResolution, int maxResolution)
            : this(type, coordinates, null, label, minResolution, maxResolution) { }

        private MapLine(MapLineType type, IEnumerable<Coordinate> coordinates, RoadSegment segment, Label label, int minResolution, int maxResolution)
            : base(minResolution, maxResolution)
        {
            this._type = type;
            this._coordinates = coordinates.ToArray();
            this._roadSegment = segment;
            this._label = label;
            this._bounds.AddRangeToBounds(coordinates);
        }

        public RoadSegment RoadSegment
        {
            get { return _roadSegment; }
            set {
                if (_roadSegment == null) throw new Exception("cannot set road segment");
                _roadSegment = value; 
            }
        }

        public Label Label
        {
            get { return _label; }
        }

        public Area Bounds
        {
            get { return _bounds; }
        }

        public MapLineType Type
        {
            get { return _type; }
        }

        public IEnumerable<Coordinate> Coordinates
        {
            get { return _coordinates; }
        }

        public override Coordinate Coordinate
        {
            get { return _bounds.Center; }
        }

        public override void Write(Stream stream, MapTile tile, bool last)
        {
            Coordinate[] coordinates = Coordinates.ToArray();
            Debug.Assert(coordinates.Length > 1);
            double tolerance = 1.415 * Math.Pow(2.0, (double)(tile.Zoom.Shift));
            coordinates = MapUtils.DouglasPeucker(coordinates, tolerance);
            BinaryWriter writer = new BinaryWriter(stream);
            ushort type = (ushort)(LINE | (0xfff & (int)this.Type));
            if (last)
                type = (ushort)(type | LAST_MAPOBJECT_FLAG);
            if (RoadSegment != null || Label != null)
            {
                type = (ushort)(type | HAS_REF);
                writer.Write(type);
                uint reference = 0;
                if (RoadSegment != null)
                    reference = (uint)(0x80000000 | (uint)(RoadSegment.Road.Id & 0x7fffffff));
                else
                    reference = (uint)(Label.Offset & 0x7fffffff);
                writer.Write(reference);
            }
            else
                writer.Write(type);
            Coordinate start = coordinates[0];
            int deltaLon = (start.Longitude >> tile.Zoom.Shift) - (tile.Center.Longitude >> tile.Zoom.Shift);
            int deltaLat = (start.Latitude >> tile.Zoom.Shift) - (tile.Center.Latitude >> tile.Zoom.Shift);
            Debug.Assert(deltaLon <= short.MaxValue);
            Debug.Assert(deltaLat <= short.MaxValue);
            Debug.Assert(deltaLon >= short.MinValue);
            Debug.Assert(deltaLat >= short.MinValue);
            writer.Write((short)deltaLon);
            writer.Write((short)deltaLat);
            byte[] data = MapUtils.Make(coordinates, tile);
            Debug.Assert(data.Length <= 0x7fff);
            if (data.Length > 0x7f)
            {
                byte len1 = (byte)(0x80 | (0x7f & (data.Length >> 8)));
                byte len2 = (byte)(0xff & data.Length);
                writer.Write(len1);
                writer.Write(len2);
            }
            else
            {
                byte len1 = (byte)(0x7f & data.Length);
                writer.Write(len1);
            }
            writer.Write(data);
        }
    }
}
